Repository navigation
Conversation
Collaborator
Author
|
Status
|
Collaborator
Author
|
Updated 2:55 AM PT - Oct 21st, 2026
❌ @robobun, your commit 08c7787 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 46105That installs a local version of the PR into your bun-46105 --bun |
This was referenced Oct 19, 2026
robobun
force-pushed
the
robobun/12d3c09e/repl-persist-declared-names
branch
from
October 20, 2026 02:54
e5f204e to
c7d0fa6
Compare
`Repl::transform_for_repl` builds its parser options by hand and left `features.auto_import_jsx` at its default, false. Under the automatic JSX runtime the parser then printed the call to the runtime function without the import that declares it, so every JSX input threw `ReferenceError: jsxDEV_7x81h0kn is not defined`. Forward the option from the VM's transpiler, as `Transpiler::parse` does for a file. The REPL transform already turns the generated import into an awaited `import()`.
robobun
force-pushed
the
robobun/f54243c9/repl-jsx-runtime-import
branch
from
October 21, 2026 09:49
6959cc7 to
c1b756a
Compare
The automatic JSX import block copied `import_source` and `package_name` into the arena before it knew whether the file used any JSX. Move each copy into the branch that generates its import. A parse with no JSX element no longer allocates there.
robobun
force-pushed
the
robobun/f54243c9/repl-jsx-runtime-import
branch
from
October 21, 2026 09:54
c1b756a to
08c7787
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #45915. The source also applies to main (Notes).
Problem
bun replthrowsReferenceError: jsxDEV_7x81h0kn is not definedunder the default (automatic) JSX runtime, at every entry.Repl::transform_for_repl(src/runtime/cli/repl.rs:2144) hand-builds its parser options and leavesfeatures.auto_import_jsxfalse. The parser then prints the call without its import (src/js_parser/parse/parse_entry.rs:2278).Fix
vm.transpiler.options.auto_import_jsx, asTranspiler::parsedoes for a file. The REPL transform already turns the import intoawait import().test/js/bun/repl/repl.test.ts(20 new tests, 17 fail on the base),repl-transform.test.ts,transpiler.test.js,bundler_jsx.test.ts.Background
<div />intojsxDEV(...), imported fromreact/jsx-dev-runtimeunder a fixed hashed name.Bun.Transpiler, Auto import jsx-runtime to fix: Can't find variable: jsxDEV #7499) andTranspiler::parse(it brings file-only options such as constant inlining).Downsides
node_modules, a JSX input asks the registry forreact, as a file does. A silent registry blocks the prompt for 36 s, and Ctrl+C cannot stop it.import()per JSX input (7ioctl, 2rt_sigactionat a terminal). Without JSX: 0 allocations, 55 instructions more. Still failing:<p>it's</p>,export.Notes
Commands that change
Rows 1 to 4: no
reactcan be resolved (anode_modulesdirectory without it, or--no-install).bun repl -e 'console.log("ran", 42); function App() { return <div /> }'ran 42, exit 0error: Cannot find module 'react/jsx-dev-runtime' from '.', nothing else, exit 1bun repl -e 'console.log("ran"); if (false) { <div /> }'ran, exit 0bun repl -e 'try { <div /> } catch (e) { console.log("caught", e.name) }'caught ReferenceError, exit 0globalThis.ready = 1; function App() { return <div /> }, then[globalThis.ready, typeof App][ 1, "function" ][ undefined, "undefined" ]node_modulesat all:bun repl -e 'console.log((<div />).type)'ReferenceError, 0 registry requestsreact, then the element or the not-found errorreactinstalled:bun repl -e 'globalThis.jsxDEV_7x81h0kn = (type) => ({ via: "my global " + type }); console.log((<div />).via)'my global divbun -eprints on 1.4.3 for the same code. The import of the runtime is the first statement of the input, so when it fails no statement runs.Origin and reach
docs/runtime/repl.mdx:28, "TypeScript & JSX"). The tracker has no report ofjsxDEV_7x81h0knor of JSX forbun repl.bun -eof the same code works, and the failure is a leaked internal name, not a diagnostic.Repro (a stand-in
react, so no third-party code runs)ReferenceError: jsxDEV_7x81h0kn is not definedfor both, and-pexits with 1. With a tsconfig"jsx": "react-jsx"the name isjsx_w77yafs4.el.typeand-pprint"div".bun -pwith the same code printsdivon every build.What the tests cover
.editorended with Ctrl+D,.load,.copy,-e,--eval,-p,--print..loadand the other entries work for input that the REPL transform accepts (see "Not fixed here").bun -eand withbun repl -eand compared: development (jsxDEV), production (jsx,jsxs), akeyafter a spread (createElementfrom the package itself), two generated imports in one input, a writtenimportbeside JSX, tsconfigjsxImportSource,--jsx-import-source, a@jsxImportSourcepragma.-eexits with 1, no statement of the input runs), and the registry request when nonode_modulesdirectory is found (a local server that answers 404).Why stacked on #45915, and what each order gives
A debug build of main
ad4089bf49with only therepl.rsline makes JSX evaluate at every entry, with these faults:function A() { return <div /> }, then an input under/** @jsxImportSource other */, thenA():Anow calls the runtime ofother. The same happens after.loadof a file with that pragma, and afterprocess.chdirto a directory with another copy ofreactfollowed by a JSX input.var react = "mine"and<div {...props} key="k" />,reactis the module namespace.keyafter a spread) add 5 names toglobalThis:jsx_dev_runtime,jsxDEV_7x81h0kn,Fragment_8vg9x3sq,react,createElement_mvmpqhxp.On #45915 with the same line, the component keeps its runtime in all three orders, user globals with those names keep their values, and 0 names are added. The tests
a component keeps the JSX runtime of its own inputandJSX input adds no name of its own to the global objecthold these.The two source hunks apply to main unchanged. If the line lands before #45915, those two tests must wait for #45915, and a session that uses two JSX runtimes sees the faults above.
Relation to open PRs
division after a JSX elementexpects aReferenceErrorfor five JSX inputs, with the comment "no JSX runtime is configured". Atest.todorow expects the same for(<a>https://x</a>). The two PRs merge without a textual conflict, but with this PR those inputs evaluate with the repository's ownreact. The PR that lands second must update those rows.parse_entry.rsand conflicts with the second commit here (git merge-tree). The first commit (therepl.rsline and the tests) merges cleanly with it. The PR that lands second keeps each copy inside the branch that generates its import.require()in a CommonJS-wrapped module. Today a file withmodule.exports = <div />is aSyntaxErrorunderbun run. The REPL does not need that path: it has no CommonJS wrapper, andawait import()is what a typed import at the prompt uses.module.exports = <div />at the prompt works with this PR and has a test.ParserOptions::initcall. It merges cleanly with this PR in either order.autoImportJSX: trueas the default ofBun.Transpiler. This PR does not depend on it.-e/-pexit code,.copy,_) #45267, replMode: decide between an object literal and a block in the parser #43770, Make--no-macrosa switch of the process #45937: each owns one item under "Costs in detail" or "Not fixed here".Costs in detail
await import()for an element, a fragment, or akeyafter a spread (of the package itself, forcreateElement). 2 for an input that holds akeyafter a spread and another element, the runtime first. A writtenimportin the same input is awaited after them.importdoes. When neither that directory nor a parent hasnode_modules, the default auto-install asks the registry:Cannot find module. Input without JSX makes 0 requests.note: Timeout downloading package manifest react.importat the prompt has the same wait. repl: interrupt a running evaluation with Ctrl+C #33411 is the open PR for Ctrl+C during an evaluation.process.chdirout of a project, a JSX input takes that registry path (1 request, in a test). After a chdir into another project it loads that project's copy, so one session can hold two copies of the runtime.Measurements
Release builds (LTO) in one build directory, made for the first version of this branch: P =
25ef6936bd(the parent of #45915 then), B =e5f204e9f8(#45915 then), H = B with this diff. #45915 was rebased since. The diff of this PR is the same. Tools: gdb 16.3 (breakpoint hit counts,stepiwith scheduler-locking,catch syscall),llvm-objdump23,size,nm -S.perf,valgrind,straceandbloatyare not installed.transform_for_repl(gdb hit count on the mimalloc entry points, B 38 / 46 / 51 against H 38 / 46 / 51 for1+1,var n: number = 1,function f(){return 1}, 3 runs each)..tsxfile with no JSX 163 -> 57 instructions,.js168 -> 60,.ts2 -> 2 (gdbstepi, B against H).transform_for_replruns 2 more instructions (one load, one store)..tsxand.js, 26 bytes). For.ts: 0 -> 0. This is a count, not a speed claim: about 105 instructions per parse is far below what a timing can show, and no test can pin it because the output is the same..text: +0 bytes for this PR (size, 106,790,986 in B and in H). Bynm -S:_parse::<false>+28,_parse::<true>+24,transform_for_repl+0. Its base replMode: keep helper imports and lowering temporaries in the wrapper #45915 is +11,264 bytes over P.parse_entry.rs:1063, runs 6 instructions where P runs 2, underbun buildandbun run). This PR changes 0 instructions in_parseoutside the JSX import gate. In_parse::<true>the compiler places two basic blocks in another order.autoImportJSXoff and on.-erun (the stand-in records each evaluation).keyafter a spread): 0 (main with the flag alone: 5).ioctland +2rt_sigactionper JSX input, 0 per JSX-free input. Anawait 1input costs the same 7 and 2 on B.node_modules(a local server): JSX-free session 0, JSX input 1 (GET /react). A typedimportof the runtime makes the same request on 1.4.3.Not fixed here
All of these are the same on 1.4.3. They show now because JSX evaluates.
<ul>Enter): each line is evaluated alone and printsSyntaxError: Unexpected token '<'. The prompt continues only on open brackets, braces and parentheses. Use parentheses,.editoror.load.<p>it's</p>): the prompt waits for more input, and on a pipe the rest of the script is dropped. The same byte scan (is_incomplete_code) decides both. repl: recognize regex literals and comments in continuation detection #37327 rewrites that scan.<div></span>): the REPL prints JavaScriptCore'sUnexpected token '<', wherebun -eprintsExpected closing JSX tag to match opening tag "<div>".transform_for_repldrops the parser's log and its callers evaluate the raw text. repl: evaluate input the REPL transform does not take through each caller's result path (-e/-pexit code,.copy,_) #45267 and replMode: decide between an object literal and a block in the parser #43770 rewrite those lines.{, has no trailing;and is a block:{ const el = <div />; console.log(el) }prints the sameSyntaxError. A trailing;makes it work. TypeScript andawaitin such a block fail the same way. An object literal ({ el: <div /> }) works and has a test.export function App() {},export defaultandexport { }printSyntaxError: Unexpected keyword 'export', also through.load. Import the file at the prompt instead:import App from "./App.tsx".Bun.Transpiler({ replMode: true })still prints the barejsxDEV_7x81h0kn(...)call unlessautoImportJSX: trueis passed. That is deliberate here: the option is opt-in at the API (Auto import jsx-runtime to fix: Can't find variable: jsxDEV #7499). Bun.Transpiler: default autoImportJSX to true for the automatic runtime #35557 is an open proposal to change the default, not a plan this PR relies on.bun pm diffparses with the same default (src/runtime/cli/pm_diff_normalize.rs:318). Its output is compared, not run.jsxDEV_7x81h0knitself beside JSX shares the name with the import at the prompt. A file refuses that source with aSyntaxError.thisas its last argument:globalThisat the prompt,undefinedin a module.Options that the prompt still does not take from the VM
transform_for_replsets each parser option by hand. After this PR the prompt still differs from a file in five settings (each row run on this build withbun -eandbun repl -e):(function, context), andaccessorfields parse(prototype, "name"), andclass C { accessor x = 1 }is aSyntaxErroremitDecoratorMetadatadesign:typeuseDefineForClassFields: false--no-macroserror: Macros are disabled--feature=Xfeature("X")is trueThe decorator row is why these are not in this PR: the switch to TC39 decorators changes input that works at the prompt today.
Self-review: what was not taken
Designs considered
Features::default().auto_import_jsx = true), or the import gate keyed onrepl_mode.Bun.Transpilerexposes the option, and when Auto import jsx-runtime to fix: Can't find variable: jsxDEV #7499 asked for the import, the answer was that the caller opts in withautoImportJSX: true.bun pm diffparses with the default too. The flag at the one caller is the smaller change.ParserOptions::init. 8 call sites change for 1 that prints code that runs, and parser: require the tsconfig options in ParserOptions::init, fix scanImports on decorators and accessor fields #45557 changes the same signature.Transpiler::parse. It would also close the five gaps above. But it sets more options from the VM that are right for a file and not for the prompt, for example constant inlining (a later input can redeclare aconst) and the nativeusing(the REPL needs the lowered form).repl_modeis not a per-parse option there, because the VM's transpiler also parses imported modules and macros. It is a possible end state, not this fix.Suites run on the debug build
test/js/bun/repl/repl.test.ts(153 pass),test/js/bun/transpiler/repl-transform.test.ts(66 pass),test/bundler/transpiler/transpiler.test.js(238 pass),test/bundler/bundler_jsx.test.ts(64 pass).test/cli/run/run-autoinstall.test.ts: 297 pass. 3 of its tests reach the 5 s timeout in the full debug run and pass when run alone. This PR does not touch that code.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/repl/repl.test.ts